package com.ruoyi.network.server;

import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import com.ruoyi.network.config.NetworkComponent;

import lombok.extern.slf4j.Slf4j;

@Component
@ConditionalOnProperty(name = "network.server.enable", havingValue = "true", matchIfMissing = false)
@Slf4j
public class ServerNetwork implements NetworkComponent {
	@Value("${network.server.innerPort:8023}") // 内网连接端口
	private Integer innerPort;
	@Value("${network.server.outerPort:8024}") // 外风连接端口
	private Integer outerPort;
	private Selector selector;
	private NioServer outerNPServer;
	private NioServer innerNpServer;
	private boolean isStop = false;
	// 热重启时部分资源(线程池、外部连接、退出死循环等)不一定能正常关闭,需要通过@PreDestroy手动关闭处理
	// 从Debug线程情况中可判断是否有关闭,若未关闭数量将一直增加
	@PreDestroy
	public void destroy() {
		log.info("====系统退出/热重启,关闭后台资源(线程池、外部连接、退出死循环等)====");
		isStop = true;
		ServerHelper.distory();
		closeServerSocketChannel(innerNpServer,selector);
		closeServerSocketChannel(outerNPServer, selector);
		if(selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @param selector 
	 * @param server 
	 * 
	 */
	private void closeServerSocketChannel(NioServer server, Selector selector) {
		if(server != null) {
			try {
				ServerSocketChannel serverChannel = server.getServerChannel();
				if(serverChannel != null && serverChannel.isOpen()) {
					serverChannel.close();
					//serverChannel.socket().close();
					serverChannel.keyFor(selector).cancel(); 
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private static void handleInput(SelectionKey key, String name, Selector selector, SocketChannel sc)
			throws Exception {
		if (key.isValid()) {
			// 处理新接入的请求消息
			if (key.isAcceptable()) {// SocketChannel(通道 )现在可以接收接连
				ServerHelper.handleAccept(name, selector, key, sc);
			} else if (key.isReadable()) {// SocketChannel(通道 )现在可以读数据
				ServerHelper.handleRead(name, selector, key);
			} else if (key.isWritable()) {// SocketChannel(通道)现在可以写数据
				ServerHelper.handleWrite(name, selector, key);
			}
		}

	}

	@Override
	public void start() {
		System.out.println(ServerNetwork.class.getName() + " 内网穿透服务器端...");
		ServerHelper.start();
		// 每个连接使用相同的selector处理
		// 创建选择器
		try {
			selector = Selector.open();
			outerNPServer = new NioServer("外网服务", outerPort.intValue(), selector);
			innerNpServer = new NioServer("内网服务", innerPort.intValue(), selector);
			// 循环遍历selector
			while (true && !isStop) {
				try {
					// int readyChannels =
					// selector.select();//阻塞直到有事件发生,会导致异步执行socketChannel.register()方法时阻塞
					int readyChannels = selector.select(100L);// 没有事件发生时,超时后必须唤醒一次,解决异步执行socketChannel.register()方法时阻塞
					if (readyChannels == 0) {
						continue;
					}
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> it = keys.iterator();
					SelectionKey key = null;
					// 轮询key
					// NIO并不能提高IO处理效率,只能提高连接数,实现一个线程处理多个通道(连接)
					while (it.hasNext()) {
						key = it.next();
						try {
							SelectableChannel channel = key.channel();
							String name = null;
							SocketChannel sc = null;
							if (channel instanceof ServerSocketChannel) {
								ServerSocketChannel ssc = (ServerSocketChannel) channel;
								if (ssc == outerNPServer.getServerChannel()) {
									name = outerNPServer.getName();
								} else {
									name = innerNpServer.getName();
								}
								sc = ssc.accept();
							} else {
								sc = (SocketChannel) channel;
							}
							handleInput(key, name, selector, sc);

						} catch (Exception e) {
							e.printStackTrace();
							ServerHelper.close(key);
						} finally {
							it.remove();
						}
					}
				} catch (Exception t) {
					t.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}
